/**
 * Copyright (c) 2018-2022, NXOS Development Team
 * SPDX-License-Identifier: Apache-2.0
 * 
 * Contains: wait queue
 * 
 * Change Logs:
 * Date           Author            Notes
 * 2023-01-17     JasonHu           Init
 */

#include <base/waitqueue.h>
#include <base/thread.h>

NX_PRIVATE NX_Error NX_WaitQueueDefaultHandler(NX_WaitEntry * entry)
{
    NX_Thread * thread = (NX_Thread *)entry->data;
    NX_ThreadUnblock(thread);

    NX_ListDelInit(&entry->list);

	return NX_EOK;
}

void NX_WaitQueueInit(NX_WaitQueue * queue)
{
    NX_SpinInit(&queue->lock);
    NX_ListInit(&queue->head);
}

void NX_WaitEntryInit(NX_WaitEntry * entry)
{
    NX_WaitEntryInitWithHandler(entry, NX_WaitQueueDefaultHandler);
}

void NX_WaitEntryInitWithHandler(NX_WaitEntry * entry, NX_WaitHandler handler)
{
    entry->data = NX_ThreadSelf();
    entry->handler = handler;
    entry->flags = 0;
    NX_ListInit(&entry->list);
}

void NX_WaitQueuePrepare(NX_WaitQueue * queue, NX_WaitEntry * entry)
{
    NX_UArch level;
    NX_SpinLockIRQ(&queue->lock, &level);
    if (NX_ListEmpty(&entry->list))
    {
        NX_ListAddTail(&entry->list, &queue->head);
    }
    NX_SpinUnlockIRQ(&queue->lock, level);
}

void NX_WaitQueueFinish(NX_WaitQueue * queue, NX_WaitEntry * entry)
{
    NX_UArch level;
    NX_SpinLockIRQ(&queue->lock, &level);
    NX_ListDelInit(&entry->list);
    NX_SpinUnlockIRQ(&queue->lock, level);
}

NX_Error NX_WaitQueueWakeUp(NX_WaitQueue * queue)
{
    NX_WaitEntry * entry, * next;
    NX_UArch level;

    if (NX_ListEmpty(&queue->head))
    {
        return NX_ENORES;
    }

    NX_SpinLockIRQ(&queue->lock, &level);
    NX_ListForEachEntrySafe(entry, next, &queue->head, list)
    {
        entry->handler(entry);
        break;
    }
    NX_SpinUnlockIRQ(&queue->lock, level);
    return NX_EOK;
}

NX_Error NX_WaitQueueWakeUpAll(NX_WaitQueue * queue)
{
    NX_WaitEntry * entry, * next;
    NX_UArch level;

    if (NX_ListEmpty(&queue->head))
    {
        return NX_ENORES;
    }

    NX_SpinLockIRQ(&queue->lock, &level);
    NX_ListForEachEntrySafe(entry, next, &queue->head, list)
    {
        entry->handler(entry);
    }
    NX_SpinUnlockIRQ(&queue->lock, level);
    return NX_EOK;
}

NX_Error NX_WaitQueueBlockUnlockTimeout(NX_WaitQueue *queue, NX_Spin *lock, NX_TimeVal tv)
{
    NX_Error err;
    NX_WaitEntry entry;

    if (queue == NX_NULL || lock == NX_NULL)
    {
        return NX_EINVAL;
    }

    if (tv == 0)
    {
        return NX_ETIMEOUT;
    }

    NX_WaitEntryInit(&entry);
    NX_WaitQueuePrepare(queue, &entry);
    NX_SpinUnlock(lock);
    err = NX_ThreadSleep(tv);
    NX_WaitQueueFinish(queue, &entry);
    /* sleep interrupted means weakup in times */
    return err == NX_EINTR ? NX_EOK : NX_ETIMEOUT;
}
